-
-
Notifications
You must be signed in to change notification settings - Fork 252
feat: JsonRpcEngineV2
#6176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: JsonRpcEngineV2
#6176
Conversation
f74dff2
to
c393838
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a test to ensure that parallel processing of RPC requests work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edit: demonstrates that multiple requests can be in flight simultaneously and that the context
is isolated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the processing was sequential the added test would still pass I think. Do we not at a minimum need an element of time difference between the requests? Ideally we can prove that requests don't always flow through in the order they are dispatched in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
866aed6 demonstrates that request are not processed serially
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had one more comment about typing context, but I have finally gone through everything in this PR so I believe that is the only thing that's left.
return next(); | ||
}); | ||
const middleware2 = jest.fn(({ context }) => { | ||
return context.get('foo') as string | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way to avoid from needing to typecast context values? I see that we have assertGet
, so I imagine that this would be the primary way to access values in the context instead of get
, but since we aren't performing any validation on values it seems like a fancy typecast.
If the request and result are generic, should the context be generic too? Perhaps we could add another type parameter to JsonRpcMiddleware
, after the request. Maybe it would work something like this:
const middleware1 = (({ context, next }) => {
return context.get('foo') < 30 ? 'bar' : next();
}) satisfies JsonRpcMiddleware<
JsonRpcRequest,
// The second parameter is now the content of the context
{ foo: number },
'bar' | true
>;
const middleware2 = (() => {
return true;
}) satisfies JsonRpcMiddleware<JsonRpcRequest, {}, true>;
const engine = new JsonRpcEngineV2<
JsonRpcRequest,
// The second parameter is now the content of the context
{ foo: number },
'bar' | true
>({
middleware: [middleware1, middleware2],
});
const request = makeRequest();
const result1 = await engine.handle(request, {
context: new MiddlewareContext({ foo: 31 }),
}); //=> 'bar'
const result2 = await engine.handle(request, {
context: new MiddlewareContext({ foo: 29 }),
}); //=> true
To do this, I suppose MiddlewareContext
would have to be a wrapper around an object rather than a subclass of Map.
Would this introduce safety issues and/or screw up compatibility between V1 and V2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I have a solution for this, will give it a go.
}; | ||
|
||
export type JsonRpcMiddleware< | ||
Request extends JsonRpcCall = JsonRpcCall, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Thought] In providing default values for these parameters are we expecting engineers to be using this type directly? If so, I wonder if that is the most ergonomic approach. I wonder if we can almost always have TypeScript infer this. Maybe not something we have to improve now, but just a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inference at the time of engine construction generally works, although if you're defining a middleware function in one place for an engine that lives elsewhere, you probably want to use this type to ensure that your middleware function is valid. Do you have any ideas as to how we would make that more ergonomic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be curious how far we could get with removing the default values. I was mainly thinking it would reduce the size of this file. But maybe it wouldn't, and perhaps you're right that in practice it wouldn't matter as developers would need to pass in these types anyway. Like I said, just a thought as I worked with this file a bit locally, not anything concrete to suggest here.
|
||
describe('requests', () => { | ||
it('returns a result from the middleware', async () => { | ||
const middleware: JsonRpcMiddleware<JsonRpcRequest> = jest.fn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Thought] I wonder if we should do another pass through these tests at some point and convert these type assertions into satisfies
? I do agree with Jongsun that in general we should be promoting use of satisfies
at least for non-final types instead of type assertions, so that we can have TypeScript infer as much as it can. I'd at least want the tests to serve as examples and represent best practices for how authors should define middleware.
Using a generic Result forced each middleware function to have the same return type. This makes the generic useless. Supporting this, almost all of our uses of the equivalent generic on the legacy JsonRpcMiddlware type are Json, any, or unknown. In consequence, we remove the Result generic. In addition, unfreezes the result returned from asLegacyMiddleware, a problem that was surfaced by the type refactor. Adds a test to verify this behavior.
Explanation
Introduces
JsonRpcEngineV2
andJsonRpcServer
, intended to replace all existing usage of the existingJsonRpcEngine
implementation. For the motivation behind this change, see #6088 and/or this ADR.Implementation
In order to resolve the problems listed in the motivation, V2 engine is split in two:
JsonRpcEngineV2
JsonRpcServer
errorHandler
constructor parameter for capturing engine / middleware errorsSee the updated package
README.md
for details.Migration
While this PR is substantial, migrating our existing JSON-RPC pipelines will be a significant project involving multiple teams over many releases cycles. To facilitate this, the PR introduces a forwards-compatibility adapter,
asV2Middleware()
, and backwards-compatibility adapter,asLegacyMiddleware()
, for the legacy and V2 engines, respectively. In addition, all V2 exports are exposed under the/v2
export path, making this update completely non-breaking (although all legacy exports are deprecated).Note to reviewers
I recommend proceeding as follows:
JsonRpcEngineV2
JsonRpcServer
References
JsonRpcEngine
for safety, ergonomics, and debuggability #6088JsonRpcEngine
Checklist
I've prepared draft pull requests for clients and consumer packages to resolve any breaking changesNote
Introduces
JsonRpcEngineV2
andJsonRpcServer
, adds v2/ exports and adapters to interoperate with the legacy engine, updates docs, tests, and package metadata.JsonRpcEngineV2
core with immutable request/result handling,MiddlewareContext
, and utilities (isRequest
,isNotification
,JsonRpcEngineError
,stringify
).JsonRpcServer
wrapper for JSON-RPC-compliant request/response handling and error serialization.v2/asLegacyMiddleware
and legacyasV2Middleware
to bridge between engines; include propagation utilities.JsonRpcEngine
and helpers as deprecated; minor internal refactors to use sharedstringify
and annotations.src/README.md
.@metamask/json-rpc-engine/v2
(ESM/CJS) and addv2.js
for Browserify compatibility.package.json
exports and add deps (deep-freeze-strict
,klona
,@types/deep-freeze-strict
).Written by Cursor Bugbot for commit 7d7ded1. This will update automatically on new commits. Configure here.